<!-- @format -->

<template>
  <div
    class="comp-image"
    :class="{
      'comp-image--round': round,
      'comp-image--circle': circle,
      'comp-image--loading': loading && showLoading,
      'comp-image--error': isError && showError,
      [`comp-image--mode-${mode}`]: true,
    }"
    :style="{
      width: width
        ? typeof width === 'number'
          ? `${width}rpx`
          : width
        : '100%',
      height: height
        ? typeof height === 'number'
          ? `${height}rpx`
          : height
        : '100%',
      borderRadius: radius ? `${radius}rpx` : '',
    }"
    @click="handleClick"
  >
    <!-- 图片 -->
    <image
      v-if="!isError || errorSrc"
      class="comp-image__img"
      :src="actualSrc"
      :mode="mode"
      :lazy-load="lazyLoad"
      :show-menu-by-longpress="showMenuByLongpress"
      :draggable="draggable"
      :webp="webp"
      @load="handleLoad"
      @error="handleError"
    />

    <!-- 加载中 -->
    <div v-if="loading && showLoading" class="comp-image__loading">
      <slot name="loading">
        <div class="comp-image__loading-icon"></div>
        <div v-if="loadingText" class="comp-image__loading-text">
          {{ loadingText }}
        </div>
      </slot>
    </div>

    <!-- 加载失败 -->
    <div v-if="isError && showError" class="comp-image__error">
      <slot name="error">
        <div class="comp-image__error-icon"></div>
        <div v-if="errorText" class="comp-image__error-text">
          {{ errorText }}
        </div>
      </slot>
    </div>
  </div>
</template>

<script setup lang="ts">
  import { ref, computed, watch } from 'vue';
  import type { ImageMode } from './types';

  interface Props {
    /** 图片资源地址 */
    src?: string;
    /** 图片裁剪、缩放的模式 */
    mode?: ImageMode;
    /** 宽度，单位rpx，或者百分比，或者auto */
    width?: number | string;
    /** 高度，单位rpx，或者百分比，或者auto */
    height?: number | string;
    /** 圆角大小，单位rpx */
    radius?: number;
    /** 是否圆形 */
    circle?: boolean;
    /** 是否圆角 */
    round?: boolean;
    /** 是否懒加载 */
    lazyLoad?: boolean;
    /** 是否开启长按图片显示识别小程序码菜单 */
    showMenuByLongpress?: boolean;
    /** 是否能拖动图片 */
    draggable?: boolean;
    /** 是否解析 webP 格式 */
    webp?: boolean;
    /** 是否显示加载中提示 */
    showLoading?: boolean;
    /** 是否显示加载失败提示 */
    showError?: boolean;
    /** 加载中提示文字 */
    loadingText?: string;
    /** 加载失败提示文字 */
    errorText?: string;
    /** 加载失败时显示的图片 */
    errorSrc?: string;
  }

  const props = withDefaults(defineProps<Props>(), {
    src: '',
    mode: 'aspectFill',
    width: 40,
    height: 40,
    radius: 8,
    circle: false,
    round: false,
    lazyLoad: false,
    showMenuByLongpress: false,
    draggable: false,
    webp: false,
    showLoading: true,
    showError: true,
    loadingText: '',
    errorText: '',
    errorSrc: '',
  });

  const emit = defineEmits<{
    /** 图片点击事件 */
    (e: 'click'): void;
    /** 图片加载完成事件 */
    (e: 'load', detail: { width: number; height: number }): void;
    /** 图片加载失败事件 */
    (e: 'error'): void;
  }>();

  // 加载状态
  const loading = ref(true);
  // 错误状态
  const isError = ref(false);

  // 监听src变化，重置状态
  watch(
    () => props.src,
    (newVal) => {
      if (newVal) {
        loading.value = true;
        isError.value = false;
      }
    },
  );

  // 实际显示的图片地址
  const actualSrc = computed(() => {
    if (isError.value && props.errorSrc) {
      return props.errorSrc;
    }
    return props.src;
  });

  // 处理图片加载完成
  const handleLoad = (event: any) => {
    loading.value = false;
    isError.value = false;
    const { width, height } = event.detail || {};
    emit('load', { width, height });
  };

  // 处理图片加载失败
  const handleError = () => {
    loading.value = false;
    isError.value = true;
    emit('error');
  };

  // 处理图片点击
  const handleClick = () => {
    emit('click');
  };

  // 预览图片
  const preview = () => {
    if (isError.value || !props.src) return;

    uni.previewImage({
      current: props.src,
      urls: [props.src],
    });
  };

  // 暴露方法
  defineExpose({
    preview,
  });
</script>

<style lang="scss" scoped>
  .comp-image {
    position: relative;
    overflow: hidden;
    display: inline-block;
    display: flex;
    align-items: center;
    justify-content: center;

    &--round {
      border-radius: 8rpx;
    }

    &--circle {
      border-radius: 50%;
    }

    &__img {
      display: block;
      width: 100%;
      height: 100%;
    }

    &__loading,
    &__error {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      background-color: #f7f8fa;
      color: #969799;
    }

    &__loading-icon {
      width: 32rpx;
      height: 32rpx;
      border: 2rpx solid #dcdee0;
      border-color: #dcdee0 #dcdee0 #dcdee0 transparent;
      border-radius: 50%;
      animation: comp-image-rotate 0.8s linear infinite;
    }

    &__loading-text {
      margin-top: 8rpx;
      font-size: 24rpx;
      color: #969799;
    }

    &__error-icon {
      width: 32rpx;
      height: 32rpx;
      position: relative;

      &::before,
      &::after {
        content: '';
        position: absolute;
        top: 50%;
        left: 50%;
        width: 32rpx;
        height: 2rpx;
        background-color: #dcdee0;
        transform: translate(-50%, -50%);
      }

      &::before {
        transform: translate(-50%, -50%) rotate(45deg);
      }

      &::after {
        transform: translate(-50%, -50%) rotate(-45deg);
      }
    }

    &__error-text {
      margin-top: 8rpx;
      font-size: 24rpx;
      color: #969799;
    }
  }

  @keyframes comp-image-rotate {
    from {
      transform: rotate(0deg);
    }
    to {
      transform: rotate(360deg);
    }
  }
</style>
